var LNSql=require('./Sql').LNSql;
var LNRR=require('./RR').LNRR;
//var jsngrr=new LNRR();
//var model_factory=require('./Model_factory').ModelFactory;
var Model=require('./Model').LNModel;
//var model_builder=require('./ModelBuilder').ModelBuilder;
var LNSessions=require('./Sessions').LNSessions;
var LNRewriter=require('./Rewriter').LNRewriter;
var LNViews=require('./Views').LNViews;
var LNModel=require('./Model').LNModel;
var LNModelGenerator=require('./ModelGenerator').LNModelGenerator;

var fs=require('fs');

require('http');


	/**
	 * Function: safe
	 *		Outputs HTML-safe variable
	 */
	function safe(t) {
		if (t==undefined) return "--undefined--";
		if (t==null) return "--null--";
		if (typeof t=="string") return t.replace(/\&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
		return t;
	};

	/**
	 * Function: Dumper
	 *		recursively Dumps object
	 */
	function Dumper(obj, indent,findent) {
		if (!indent) indent="";
		if (!findent) findent="";
		if (indent.length>4) return findent+"...\n";
		if (obj==null) return findent+"null\n";
		if (obj==undefined) return findent+"undefined\n";
		if (obj.toJSON) obj=obj.toJSON();
		switch (typeof obj) {
			case "string":
				return findent+'"'+obj.replace(/"/g,"\\\"")+'"\n';
			case "number":
			case "boolean":
				return findent+obj+'\n';
			case "object":
				if (obj.constructor==Array) {
					var ret=findent+" [\n";
					for (var i=0;i<obj.length;i++) {
						ret+=Dumper(obj[i],indent+"\t",indent+"\t");
					}
					ret+=indent+"]\n";
					return ret;
				} else if (obj.constructor.toString().match(/Error|Exception/)) {
					var ret=obj.toString();
					if (obj.stack) ret+="\nStack\n"+obj.stack+"\n";
					return ret;
				} else {
					ret=findent+"{";
					if (obj.constructor!=Object) ret+=" /*"+obj.constructor+"*/";;
					ret+="\n";
					var arr=[];
					for (var item in obj) arr.push(item);
					arr.sort();
					arr.forEach(function(item) {
						if (item.charAt(0)=='_') return;
						ret+=indent+"\t"+item+": "+Dumper(obj[item],indent+"\t","");
					});
					ret+=indent+"}\n";
					return ret;
				}
				break;
			default:
				return findent+(typeof obj)+'\n';
		}
	}



/**
 * Class: LNServer
 *		Server is responsible for serving http queries and console requests.
 *
 * Properties:
 * iterations typeof Integer		- Number of successull HTTP iterations
 * paths_rules typeof Object		- Generic rules of filesystem paths placement
 * sites typeof Object				- A hash of sites, e.g. this.sites["www.mysite.com"]=SiteObject;
 * sites_by_domain typeof Object	- A hash of sites, e.g. this.sites_by_domain["mysite.com"]=SiteObject;
 * default_site typeof SiteObject
 * options typeof Object			- Configuration of server, fetched from config/server.js and config/server-local.js
 * auth_engine typeof LNSessions
 *
 *
 */

/**
 * Method: Constructor
 *		Creates a server itself.
 *
 *
 */
function LNServer()
{
	this.iterations=0;
	this.require_errors="";
	this.paths_rules={
		html: "html/",
		sql: "code/sql/",
		views: "code/views/",
		cache: "cache/",
		database: "database/",
		i18n: "i18n/",
		ssjs: "code/",
		models_config: "database/models.js"
	};
	this.Dumper=Dumper;
	this.SafeDumper=function(x) { return safe(Dumper(x)); };
	this.auth_engine=new LNSessions();
	this.sites_by_name={};
	this.cwd=system.getcwd();
	this.views=new LNViews();
//	system.stdout.writeLine(this.cwd);
	this.init();
	return this;
}

/**
 * Method: init
 *		Initializes server.
 *		calls init_system() and lazy_init_sites().
 *
 */
LNServer.prototype.init=function() {
	this.init_system();
	this.lazy_init_sites();
}

/**
 * Method: init_system
 *		Initializes base system, reads config file, loads global modules and functions
 *
 */
LNServer.prototype.init_system=function() {
	
	this.options={
		system:require(this.cwd+'/config/server.js').system
	}
	this.options.system.LNModelGenerator=LNModelGenerator;
	var f=new fs.File(this.cwd+'/config/server.local.js');
	if (f.exists()) {
		var t=require(this.cwd+'/config/server.local.js').system;
		for (var k in t) {
			if (k=="load") {
				this.options.system[k]=this.options.system[k].concat(t[k]);
			} else {
				this.options.system[k]=t[k];
			}
		}
	}
	var p=this.options.system.paths;
	var h=this.options.system.paths={};
	for (var i=0;i<this.options.system.basepaths.length;i++) {
		var bp=this.options.system.basepaths[i];
		if (bp==".") bp=this.cwd+"/";
		for (var k in p) {
			if (p[k].match(/^\//)) continue;
			var newval;
			if (k=="uploads_subfolder") {
				newval=p[k];
			} else {
				newval=bp+p[k];
			}
			if (!h[k]) h[k]=[];
			h[k].push(newval);
		}
	}
//	if (this.options.system.code=="siting") throw Dumper(this.options.system);
	this.init_subsystem(this.options.system,1);
}

/**
 * Method: init_subsystem
 *		Initializes given subsystem (i.e. base system or site-specific)
 *
 * Parameters:
 *		config - Configuration file for given subsystem
 *		is_system - Flag is given a base system or site-specific config
 */
LNServer.prototype.init_subsystem=function(config,is_system)
{
	var a=["functions","controllers","models"];
	config.functions={};
	config.controllers={};
	config.models={};
	if (!is_system) this.init_subsystem_sitecopy(config);
/*	for (var i=0;i<a.length;i++) {
		var ai=a[i];
		config[ai]={};
		if (!is_system) {
			var h=this.options.system[ai];
			switch(ai) {
				case "models":
					for (var k in h) {
						config.models[k]=new LNModel(config.sql,h[k]);
//						config[ai][k].prototype={};
						for (var k2 in h[k].methods) config.models[k][k2]=h[k].methods[k2];
					}
					break;
				case "controllers":
					for (var k in h) {
						config[ai][k]=h[k];
					}
					break;
				case "functions":
					for (var k in h) {
						config.functions[k]={};
						for (var kk in h[k]) config[ai][k][kk]=h[k][kk];
					}
					break;
			}
		}
	}*/
	this.init_subsystem_loader(config,is_system,config.paths.ssjs,"models/_auto.js");
	this.init_subsystem_loader(config,is_system,config.paths.ssjs,"controllers/_auto.js");
	if (config.load) {
		for (var i=0;i<config.load.length;i++) this.init_subsystem_loader(config,is_system,config.paths.ssjs,config.load[i]);
	}
}

LNServer.prototype.init_subsystem_sitecopy=function(config)
{
	var a=["functions","controllers","models"];
	for (var i=0;i<a.length;i++) {
		var ai=a[i];
		var h=this.options.system[ai];
		switch(ai) {
			case "models":
				for (var k in h) {
					config.models[k]=new LNModel(config.sql,h[k]);
//					config[ai][k].prototype={};
					for (var k2 in h[k].methods) config.models[k][k2]=h[k].methods[k2];
				}
				break;
			case "controllers":
				for (var k in h) {
					config[ai][k]=h[k];
				}
				break;
			case "functions":
				for (var k in h) {
					config.functions[k]={};
					for (var kk in h[k]) config[ai][k][kk]=h[k][kk];
				}
				break;
		}
	}
}

/**
 * Method: init_subsystem_loader
 *		
 *
 * Parameters:
 *		config					- Configuration file for given subsystem
 *		is_system				- Flag is given a base system or site-specific config
 *		path					- array or string
 *		filename typeof String	- javascript file
 */
LNServer.prototype.init_subsystem_loader=function(config,is_system,paths,shortname)
{
	var filename;
	if (paths.constructor==Array) {
		for (var i=0;i<paths.length;i++) {
			filename=paths[i]+shortname;
			var f=new fs.File(filename);
			if (f.exists()) i=paths.length;
		}
	} else {
		filename=paths+shortname;
	}
	var r;
	try {
		r=require(filename).add;
	} catch (e) {
		this.require_errors+="is_system="+is_system+", filename="+filename+"\n"+e+"\n";
		return;
	}
	if (!r) {
		this.require_errors+="is_system="+is_system+", filename="+filename+"\nrequire().add is undefined\n";
		return;
	}
	for (var i=0,s=r.length;i<s;i++) {
		switch (r[i]._type) {
			case "controller":
				this.init_controller(config,is_system,r[i]);
				break;
			case "controller_modes":
				this.init_controller_modes(config,is_system,r[i]);
				break;
			case "functions":
				this.init_functions(config,is_system,r[i]);
				break;
			case "model":
				this.init_model(config,is_system,r[i]);
				break;
			case "model_methods":
				this.init_model_methods(config,is_system,r[i]);
				break;
			default:
				//throw "Unknown _type "+r[i]._type;
				this.require_errors+="is_system="+is_system+", filename="+filename+"\nunknown )type, add["+i+"]._type="+r[i]._type+"\n";
				break;
		}
	}
}

/**
 * Method: init_controller
 *		
 *
 * Parameters:
 *		config					- Configuration file for given subsystem
 *		is_system				- Flag is given a base system or site-specific config
 *		obj typeof Object		- Controller source object
 */
LNServer.prototype.init_controller=function(config,is_system,obj)
{
	if (config.controllers[obj._config.name]) {
		if (obj._config.name.match(/AutoAdmin/)) {
		} else if (obj._config.override_ok) {
			var tmp={};
			var orig=config.controllers[obj._config.name];
			for (var k in orig) tmp[k]=orig[k];
			for (var k in obj) tmp[k]=obj[k];
			config.controllers[obj._config.name]=tmp;
			return;
		} else {
			throw new Error("Initializing "+(is_system?"system":"site")+" - controller '"+obj._config.name+"' already exists, use _config.override_ok=true option!");
		}
	}
	config.controllers[obj._config.name]=obj;
}

/**
 * Method: init_controller_modes
 *		
 *
 * Parameters:
 *		config					- Configuration file for given subsystem
 *		is_system				- Flag is given a base system or site-specific config
 *		obj typeof Object		- Controller source object
 */
LNServer.prototype.init_controller_modes=function(config,is_system,obj)
{
	if (!config.controllers[obj._config.name]) throw new Error("Initializing "+(is_system?"system":"site")+" - controller modes of controller '"+obj._config.name+"', does not exist yet!");
	for (var k in obj) {
		if (k.match(/^_/)) continue;
		config.controllers[obj._config.name][k]=obj[k];
	}
}

/**
 * Method: init_model
 *		
 *
 * Parameters:
 *		config					- Configuration file for given subsystem
 *		is_system				- Flag is given a base system or site-specific config
 *		obj typeof Object		- Model source object
 */
LNServer.prototype.init_model=function(config,is_system,obj)
{
	if (is_system) {
  		config.models[obj._config.name]=obj;
	} else {
		config.models[obj._config.name]=new LNModel(config.sql,obj);
	}
}

/**
 * Method: init_model_methods
 *		
 *
 * Parameters:
 *		config					- Configuration file for given subsystem
 *		is_system				- Flag is given a base system or site-specific config
 *		obj typeof Object		- Model source object
 */
LNServer.prototype.init_model_methods=function(config,is_system,obj)
{
	if (!config.models[obj._config.name]) {
		this.require_errors+="Model '"+obj._config.name+"' not found, mode="+(is_system?"system":"site")+", config.code="+config.code+"\n";
	//	throw new Error("Model '"+obj._config.name+"' not found, mode="+(is_system?"system":"site")+", config.code="+config.code);
		return;
	}
	for (var k in obj) {
		if (k=="_config") continue;
		if (k=="_type") continue;
		if (is_system) {
	  		config.models[obj._config.name].methods[k]=obj[k];
		} else {
			config.models[obj._config.name][k]=obj[k];
		}
	}
}
/**
 * Method: init_functions
 *		
 *
 * Parameters:
 *		config					- Configuration file for given subsystem
 *		is_system				- Flag is given a base system or site-specific config
 *		obj typeof Object		- Controller source object
 */
LNServer.prototype.init_functions=function(config,is_system,obj)
{
	var h=config.functions[obj._section];
	if (!h) h=config.functions[obj._section]={};
	for (var k in obj) {
		if (k=="_type") continue;
		h[k]=obj[k];
	}
}

/**
 * Method: lazy_init_sites
 *		Lazily initializes sites in config/sites-enabled. Means that only critical data is loaded,
 *		all modules, database connections and things will be initialized on-demand.
 *
 */
LNServer.prototype.lazy_init_sites=function() {
	var dir=new fs.Directory(this.cwd+"/config/sites-enabled");
	var files=dir.listFiles();
	for (var i=0,s=files.length;i<s;i++) {
		var x=files[i];
		if (!x.match(/\.js$/)) continue;
		this.lazy_init_site(require(this.cwd+'/config/sites-enabled/'+x).site);
	}
}

/**
 * Method: lazy_init_site
 *		Lazily initializes one exact site
 *
 * Parameters:
 *		site typeof SiteObject - Site config object
 */
LNServer.prototype.lazy_init_site=function(site)
{
	if (!site) return;
	for (var i=0,s=site.names.length;i<s;i++) {
		this.sites_by_name[site.names[i]]=site;
	}
}

/**
 * Method: init_site
 *		Fully initializes one exact site, called on demand
 *
 * Parameters:
 *		site typeof SiteObject - Site object
 */
LNServer.prototype.init_site=function(site)
{
	if (site.initialized) return;
	site.initialized=true;
	// Init paths
	for (var p in this.paths_rules) {
		if (site.paths[p]) continue;
		site.paths[p]=site.paths.basepath+this.paths_rules[p];
	}

	site.viewsconfig={
		views:[site.paths.views].concat(this.options.system.paths.views),
		cache:[site.paths.cache].concat(this.options.system.paths.cache)
	};

	// Init SQL
	site.sql=new LNSql(site.database,[site.paths.sql].concat(this.options.system.paths.sql));
	if (site.log_sql_queries) site.sql.log_enabled=1;
	
	//site.mfact=new model_factory(site,space.functions.Model,Dumper);
	
	// Init rewriter
	site.rewriter=new LNRewriter(site.sql);

	// Load locales (TODO)

	// Init subsystem
	this.init_subsystem(site,0);
//	throw Dumper(site);

	// Load vars (if any)
	site.functions.Files.load_site_vars.call({site:site});
//	if (site.code=="14") throw site.vars;
//	if (site.code=="14") throw site;
}

/**
 * Method: console_work
 *		Executed when called from console
 *
 * Parameters:
 *		args typeof Array - Call arguments
 */
LNServer.prototype.console_work=function(args)
{
	var h={
		mode: "help",
/*		cnt:1,
		site: "test-jsngcms.vcity.ru",
		page: "/"*/
	};

	for (var i=1;i<system.args.length;i+=2) {
		h[system.args[i].replace(/^--/,"")]=system.args[i+1];
	}
	switch (h.mode) {
		case "help":
			system.stdout.writeLine("LATTE NOIR framework, console mode");
			system.stdout.writeLine("Usage:");
			system.stdout.writeLine("	"+system.args[0]+" --mode url --site mysite.name --page / [--cnt 1]");
			system.stdout.writeLine("	"+system.args[0]+" --mode function --site mysite.name --function MySection.function_name --ARG-0 hello --ARG-1 world");
			break;
		case "url":
			var get={};
			for (var k in h) {
				var arr=k.match(/ARG-(.+)/);
				if (!arr) continue;
				get[arr[1]]=h[k];
			}
			//system.stdout.writeLine(JSON.stringify(get,undefined,"  "));
			var rcons=require("http").ServerResponse;
			this.http_work({
					method: "GET",
					silent: h.cnt?1:0,
					get: get,
					files: {},
					uri: h.page,
					server_name: h["site"],
					cookie: {}
				},
				new rcons(system.stdout,function (a,b) {} )
			);
			break;
		case "function":
			var rcons=require("http").ServerResponse;
			var args=[];
			for (var k in h) {
				var arr=k.match(/ARG-(\d+)/);
				if (arr) args[arr[1]]=h[k];
			}
			this.function_work({
					method: "GET",
					silent: h.cnt?1:0,
					get: h,
					files: {},
					uri: h.page,
					server_name: h["site"],
					function_name: h["function"],
					cookie: {}
				},
				new rcons(system.stdout,function (a,b) {}),
				args
			);
			break;
		default:
			system.stdout.writeLine("Unknown mode "+h.mode);
	}	
}

/**
 * Method: function_work
 *		Evaluates one function call, resulting in HTML or JSON or an image or whatever
 *
 * Parameters:
 *		req - HTTP request
 *		resp - HTTP response
 */
LNServer.prototype.function_work=function(req,resp,args)
{
	var d1=new Date();
	//var rr=jsngrr.create(req,resp,this);
	var rr=new LNRR(req,resp,this);
	var site=this.guess_site(rr);
	if (!site) throw new Error("No site configured for this hostname");
	rr.set_site(site);
	this.init_site(site);
	site.sql.logs={};
	this.configure(rr);
	rr.server_require_errors=this.require_errors;
	rr.auth_error=this.auth_engine.work(rr);
	var fn=req["function_name"].match(/^(\w+)\.(\w+)$/);
	rr.F(fn[1],fn[2],args[0],args[1],args[2],args[3]);
	this.deconfigure(rr);
	site.sql.commit();
}


/**
 * Method: http_work
 *		Evaluates one http call, resulting in HTML or JSON or an image or whatever
 *
 * Parameters:
 *		req - HTTP request
 *		resp - HTTP response
 */
LNServer.prototype.http_work=function(req,resp)
{
	var d1=new Date();
	//var rr=jsngrr.create(req,resp,this);
	var rr=new LNRR(req,resp,this);
	var site=this.guess_site(rr);
	if (!site) {
		rr.ln_response.render_http_error(undefined,404,"No site configured for this hostname");
		rr.ln_response.send();
		return;
	}
	
	if (site.worker && site.worker.redirect_missing_slash) {
		if (rr.missed_slash && rr.req.method=='GET') {
			var url = rr.action;
			var i=0;
			for (var k in this.fields) {
				if (!i) url+="?"; else url+="&";
				url+=encodeURIComponent(k)+"="+encodeURIComponent(this.fields[k]);
				i++;
			}
			rr.ln_response.render_301_redirect(url);
			rr.ln_response.send();
			return;
		}
	}
	
	if (site.worker && site.worker.http_debug) {
		try {
			var f=new fs.File("/tmp/"+site.code+".debug");
			f.open("a");
			for (var k in system.env) {
				f.write("sytem.env."+k+"="+system.env[k]+"\n");
			}
			for (var k in req.get) {
				f.write("request.get."+k+"="+req.get[k]+"\n");
			}
			for (var k in req.post) {
				f.write("request.post."+k+"="+req.post[k]+"\n");
			}
			f.write("\n\n");
			f.close();
		} catch(e) {
		}
	}
	try {
		this.iterations++;
		rr.set_site(site);
		this.init_site(site);
		if (this.output_file(site,rr)) {
		} else {
			site.sql.logs={};
			this.configure(rr);
			rr.server_require_errors=this.require_errors;
			rr.auth_error=this.auth_engine.work(rr);
			rr.do_rewrites();
			if (!rr.ln_response.silent && rr.action) rr.ln_response.output=rr.process_page();
		}
		this.deconfigure(rr);
	} catch(e) {
		rr.ln_response.render_500_error(rr.action,safe(Dumper(e)));
		try {
			if (site.sql) {
				site.sql.rollback();
				rr.F("Admin","send_error_notification",e);
			}
		} catch (_not_used) {}
	}
	rr.ln_response.send(rr);
	if (site.sql.log_enabled && rr.ln_response.response_headers["Content-Type"]=='text/html; charset=utf-8' && !rr.fields.ajaj && !rr.ln_response.silent && rr.ln_response.output[0]=="<") {
		rr.ln_response.res.write(site.sql.logs_to_html_comment());
	}
	try {
		site.sql.commit();
		if (rr.timed_run && rr.ln_response.status_code==200 && rr.page_type=="regular") {
			rr.timed_run.add("global",d1);
			var act=rr.action||rr.action_src;
			if (rr.fields.mode) act+="?mode="+rr.fields.mode;
			rr.timed_run.send(site.timed_run,site.code,rr.action_src,act);
		}
	} catch(e) {
		rr.F("Admin","send_error_notification",e);
	}
}

/**
 * Method: output_file
 */
LNServer.prototype.output_file=function(site,rr)
{
	var htmlpaths=[rr.site.paths.html].concat(rr.system.paths.html);
	for (var i=0;i<htmlpaths.length;i++) {
		var htmlpath=htmlpaths[i];
		var f=new fs.File(htmlpath+rr.action.replace(/\/$/,"").replace(/^\//,""));
		if (!f.exists() || !f.isFile()) continue;
		var ext=rr.action.match(/\.(\w+)/);
		ext=ext?ext[1]:"txt";
		f.open("r");
		rr.res.status(200);
		rr.ln_response.response_headers["Content-Type"]=rr.F("Files","guess_mime",ext);
		rr.res.header(rr.ln_response.response_headers);
		rr.res.write(f.read());
		rr.ln_response.silent=1;
		f.close();
		return 1;
	}
	return 0;
}



/**
 * Method: guess_site
 *		Guesses site
 *
 * Parameters:
 *		rr
 *
 * Returns:
 *		typeof SiteObject - Site or null
 */
LNServer.prototype.guess_site=function(rr)
{
	var site=this.sites_by_name[rr.server_name];
	if (!site && this.sites_by_domain) site=this.site_by_domain[rr.server_domain];
	if (!site && this.default_site) site=this.default_site;
	return site;
}

/**
 * Method: configure
 *
 * Parameters:
 *		rr typeof LNRR - request-response object
 */
LNServer.prototype.configure=function(rr)
{
	this.views.set_globalspace(rr);
	this.views.set_paths(
		rr.site.viewsconfig.views,
		rr.site.viewsconfig.cache
	);
	rr.site.sql.set_fallback_href(rr.fields,rr);
	rr.views=this.views;
//	rr.mfact=site.mfact;
//	rr.controllers=site.refs.controllers;
//	rr.models=site.refs.models;
//	rr.functions=site.refs.functions;

}

/**
 * Method: deconfigure
 *
 * Parameters:
 *		rr typeof LNRR - request-response object
 */
LNServer.prototype.deconfigure=function(rr)
{
	this.views.set_globalspace(rr);
	this.views.set_paths(this.options.system.paths.views,this.options.system.paths.cache);
	rr.site.sql.set_fallback_href({});
//	rr.mfact=null;
//	rr.controllers=this.options.system.refs.controllers;
//	rr.models=this.options.system.refs.models;
//	rr.functions=this.options.system.refs.functions;
}


exports.LNServer=LNServer;

